﻿/* Autor: Achim Schnell */

using System;
using System.Linq;
using System.ComponentModel;
using System.Reflection;
using AppFx.Reflection.Attributes;
using System.Collections.Generic;

namespace AppFx.ComponentModel.Descriptive
{
	/// <summary>
	/// Diese Klasse soll die Ableitung von PropertyDesciptor vereinfachen,
	/// indem viele Standardfunktionen bereits implementiert werden.
	/// </summary>
	public abstract class PropertyDescriptorEx : PropertyDescriptor
	{
		#region Fields

		/// <summary>
		/// Gibt oder setzt den Typ der Komponente deren Eigenschaft beschrieben wird zurück.
		/// </summary>
		protected Type _ComponentType;

		/// <summary>
		/// Gibt oder setzt die Informationen zur beschriebenen Eigenschaft.
		/// </summary>
		private PropertyInfo _PropertyInfo;

		/// <summary>
		/// Gibt den Typ der beschriebenen Eigenschaft zurück
		/// </summary>
		protected Type _PropertyType;

		/// <summary>
		/// Gibt zurück, ob die Eigenschaft schreibgeschützt ist
		/// </summary>
		protected bool _IsReadOnly;

		/// <summary>
		/// Gibt den Wert des DescriptionAttributes zurück.
		/// </summary>
		protected bool _Browsable;

		/// <summary>
		/// Gibt den durch das Attribut 'DisplayNameAttribute' festgelegten Namen
		/// oder falls nicht festgelegt den Namen der Eigenschaft zurück.
		/// (String.IsNullOrWhiteSpace(DisplayName) gilt als nicht festgelegt.
		/// </summary>
		protected string _DisplayName;

		/// <summary>
		/// Gibt den Wert des CategoryAttributes zurück.
		/// </summary>
		protected string _Category;

		/// <summary>
		/// Gibt den Wert des DescriptionAttributes zurück.
		/// </summary>
		protected string _Description;

		/// <summary>
		/// Gibt den Wert des SerializableAttributes zurück.
		/// </summary>
		protected bool _Serializable;

		/// <summary>
		/// Gibt an, ob die Eigenschaft einen Standard-Wert hat. (DefaultValueAttribute)
		/// </summary>
		protected bool _HasDefaultValue;

		/// <summary>
		/// Gibt den Wert des DefaultValueAttributs zurück. (falls gesetzt)
		/// </summary>
		protected object _DefaultValue;

		#endregion

		#region Properties

		/// <summary>
		/// Gibt den Typ der Komponente deren Eigenschaft beschrieben wird zurück.
		/// </summary>
		public override Type ComponentType
		{
			get { return _ComponentType; }
		}

		/// <summary>
		/// Gibt den Typ der beschriebenen Eigenschaft zurück
		/// </summary>
		public override Type PropertyType
		{
			get { return _PropertyType; }
		}

		/// <summary>
		/// Gibt oder setzt die Informationen zur beschriebenen Eigenschaft.
		/// </summary>
		public PropertyInfo PropertyInfo
		{
			get { return _PropertyInfo; }
			protected set { _PropertyInfo = value; }
		}

		/// <summary>
		/// Gibt zurück, ob die Eigenschaft schreibgeschützt ist
		/// Standard: False.
		/// </summary>
		public override bool IsReadOnly
		{
			get { return _IsReadOnly; }
		}

		/// <summary>
		/// Gibt den Wert des BrowsableAttributes zurück.
		/// Standard: True.
		/// </summary>
		public override bool IsBrowsable
		{
			get
			{
				return _Browsable;
			}
		}

		/// <summary>
		/// Gibt den durch das Attribut 'DisplayNameAttribute' festgelegten Namen
		/// oder falls nicht festgelegt den Namen der Eigenschaft zurück.
		/// (String.IsNullOrWhiteSpace(DisplayName) gilt als nicht festgelegt.
		/// </summary>
		public override string DisplayName
		{
			get
			{
				return _DisplayName;
			}
		}

		/// <summary>
		/// Gibt den Wert des CategoryAttributes zurück.
		/// </summary>
		public override string Category
		{
			get
			{
				return _Category;
			}
		}

		/// <summary>
		/// Gibt den Wert des DescriptionAttributes zurück.
		/// </summary>
		public override string Description
		{
			get
			{
				return _Description;
			}
		}


		#endregion

		#region Constructors

		/// <summary>
		/// Erstellt eine neue Instanz der <see cref="PropertyDescriptorEx"/>-Klasse.
		/// </summary>
		/// <param name="componentType">Typ der Komponente deren Eigenschaft beschrieben werden soll.</param>
		/// <param name="propertyInfo">Die Informationen zur Eigenschaft die beschrieben werden soll.</param>
		public PropertyDescriptorEx(Type componentType, PropertyInfo propertyInfo)
			: this(componentType, propertyInfo, propertyInfo.Name) { }

		/// <summary>
		/// Erstellt eine neue Instanz der <see cref="PropertyDescriptorEx"/>-Klasse.
		/// </summary>
		/// <param name="componentType">Typ der Komponente deren Eigenschaft beschrieben werden soll.</param>
		/// <param name="propertyInfo">Die Informationen zur Eigenschaft die beschrieben werden soll.</param>
		/// <param name="name">Der Name, der als Name der Komponente gesetzt werden soll.</param>
		public PropertyDescriptorEx(Type componentType, PropertyInfo propertyInfo, string name)
			: base(name, null)
		{
			if (componentType == null)
				throw new ArgumentNullException("componentType");
			if (propertyInfo == null)
				throw new ArgumentNullException("propertyInfo");

			// Basis-Informationen speichern
			_ComponentType = componentType;
			_PropertyInfo = propertyInfo;
			_PropertyType = propertyInfo.PropertyType;

			SetAttributeDrivenProperties(propertyInfo);
		}

		public PropertyDescriptorEx(Type componentType, Type propertyType, Attribute[] attributes, string name)
			: base(name, null)
		{
			if (componentType == null)
				throw new ArgumentNullException("componentType");

			// Basis-Informationen speichern
			_ComponentType = componentType;
			_PropertyType = propertyType;

			SetAttributeDrivenProperties(attributes);
		}


		#endregion

		#region Methods

		/// <summary>
		/// Prüft, ob der Eigenschaftswert serialisiert werden muss.
		/// </summary>
		/// <param name="component">Die Komponente die zu prüfen ist.</param>
		/// <returns>Ein Wert, der indiziert, ob der Eigenschaftswert der Komponente serialisiert werden muss.</returns>
		public override bool ShouldSerializeValue(object component)
		{
			if (component == null)
				throw new ArgumentNullException("component");
			return _Serializable;
		}

		/// <summary>
		/// Prüft, ob der Eigenschaftswert zurückgesetzt werden kann. (Bzw. ob das DefaultValueAttribute gesetzt wurde)
		/// </summary>
		/// <param name="component">Die Komponente, die auf die Fähigkeit des Zurücksetzens zu prüfen ist.</param>
		/// <returns>
		/// true, wenn durch Zurücksetzen der Komponente deren Wert geändert wird, andernfalls false.
		/// </returns>
		public override bool CanResetValue(object component)
		{
			if (component == null)
				throw new ArgumentNullException("component");
			return _HasDefaultValue;
		}

		/// <summary>
		/// Setzt den Wert der Eigenschaft auf den Standardwert zurück. (DefaultValueAttribute)
		/// </summary>
		/// <param name="component">Die Komponente mit dem Eigenschaftswert, der auf den Standardwert zurückgesetzt werden soll.</param>
		public override void ResetValue(object component)
		{
			if (component == null)
				throw new ArgumentNullException("component");
			if (CanResetValue(component))
				SetValue(component, _DefaultValue);
		}

		/// <summary>
		/// Setzt die attribut-bestimmten Eigenschaften.
		/// </summary>
		/// <param name="propertyInfo">Die Eigenschaftsinformationen, welche zum Auslesen von verwendeten Attributen verwendet werden.</param>
		protected virtual void SetAttributeDrivenProperties(PropertyInfo propertyInfo)
		{
			if (propertyInfo == null)
				throw new ArgumentNullException("propertyInfo");

			BrowsableAttribute[] browseables = null;
			_Browsable = (AttributeHelper.IsAttributeUsed<BrowsableAttribute>(propertyInfo, ref browseables, true))
				? AttributeHelper.GetAttributeValue<bool>(browseables[0], "Browsable") //: extract AttributeValue
				: true; //: use the default value

			ReadOnlyAttribute[] readOnlies = null;
			_IsReadOnly = (AttributeHelper.IsAttributeUsed<ReadOnlyAttribute>(propertyInfo, ref readOnlies, true))
				? AttributeHelper.GetAttributeValue<bool>(readOnlies[0], "IsReadOnly") //: extract AttributeValue
				: false; //: use the default value

			_Serializable = true;

			DefaultValueAttribute[] defaults = null;
			_HasDefaultValue = AttributeHelper.IsAttributeUsed<DefaultValueAttribute>(propertyInfo, ref defaults, true);
			_DefaultValue = _HasDefaultValue ? AttributeHelper.GetAttributeValue<object>(defaults[0], "Value") : null;

			DisplayNameAttribute[] displayNames = null;
			_DisplayName = (AttributeHelper.IsAttributeUsed<DisplayNameAttribute>(propertyInfo, ref displayNames, true))
				? AttributeHelper.GetAttributeValue<string>(displayNames[0], "DisplayName") //: extract AttributeValue
				: Name; //: use the default value

			CategoryAttribute[] categories = null;
			_Category = (AttributeHelper.IsAttributeUsed<CategoryAttribute>(propertyInfo, ref categories, true))
				? AttributeHelper.GetAttributeValue<string>(categories[0], "Category") //: extract AttributeValue
				: String.Empty; //: use the default value

			DescriptionAttribute[] descriptions = null;
			_Description = (AttributeHelper.IsAttributeUsed<DescriptionAttribute>(propertyInfo, ref descriptions, true))
				? AttributeHelper.GetAttributeValue<string>(descriptions[0], "Description") //: extract AttributeValue
				: String.Empty; //: use the default value
		}

		/// <summary>
		/// Setzt die attribut-bestimmten Eigenschaften.
		/// </summary>
		/// <param name="attributes">Die zu verwendenden Attribute.</param>
		protected virtual void SetAttributeDrivenProperties(Attribute[] attributes)
		{
			if (attributes == null)
				throw new ArgumentNullException("attributes");

			IEnumerable<BrowsableAttribute> browseables = attributes.Where(attr => attr.GetType() == typeof(BrowsableAttribute)).Cast<BrowsableAttribute>();
			_Browsable = (browseables != null && browseables.Count() > 0)
				? AttributeHelper.GetAttributeValue<bool>(browseables.First(), "Browsable") //: extract AttributeValue
				: true; //: use the default value

			IEnumerable<ReadOnlyAttribute> readOnlies = attributes.Where(attr => attr.GetType() == typeof(ReadOnlyAttribute)).Cast<ReadOnlyAttribute>();
			_IsReadOnly = (readOnlies != null && readOnlies.Count() > 0)
				? AttributeHelper.GetAttributeValue<bool>(readOnlies.First(), "IsReadOnly") //: extract AttributeValue
				: false; //: use the default value

			_Serializable = true;

			IEnumerable<DefaultValueAttribute> defaults = attributes.Where(attr => attr.GetType() == typeof(DefaultValueAttribute)).Cast<DefaultValueAttribute>();
			_HasDefaultValue = (defaults != null && defaults.Count() > 0);
			_DefaultValue = _HasDefaultValue ? AttributeHelper.GetAttributeValue<object>(defaults.First(), "Value") : null;

			IEnumerable<DisplayNameAttribute> displayNames = attributes.Where(attr => attr.GetType() == typeof(DisplayNameAttribute)).Cast<DisplayNameAttribute>();
			_DisplayName = (displayNames != null && displayNames.Count() > 0)
				? AttributeHelper.GetAttributeValue<string>(displayNames.First(), "DisplayName") //: extract AttributeValue
				: Name; //: use the default value

			IEnumerable<CategoryAttribute> categories = attributes.Where(attr => attr.GetType() == typeof(CategoryAttribute)).Cast<CategoryAttribute>();
			_Category = (categories != null && categories.Count() > 0)
				? AttributeHelper.GetAttributeValue<string>(categories.First(), "Category") //: extract AttributeValue
				: String.Empty; //: use the default value

			IEnumerable<DescriptionAttribute> descriptions = attributes.Where(attr => attr.GetType() == typeof(DescriptionAttribute)).Cast<DescriptionAttribute>();
			_Description = (descriptions != null && descriptions.Count() > 0)
				? AttributeHelper.GetAttributeValue<string>(descriptions.First(), "Description") //: extract AttributeValue
				: String.Empty; //: use the default value
		}

		#endregion
	}
}
